package com.lagou.edu.factory;


import com.lagou.edu.anno.MyAutowired;
import com.lagou.edu.anno.MyService;
import com.lagou.edu.anno.Transactional;
import org.reflections.Reflections;

import javax.servlet.annotation.WebServlet;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Set;

/**
 * @Description: 可识别出注解的 BeanFactory
 * @Author 50705 2021/9/15
 *
 * 一、编程题
 *
 * 学员自定义@Service、@Autowired、@Transactional注解类，完成基于注解的IOC容器（Bean对象创建及依赖注入维护）和声明式事务控制，写到转账工程中，并且可以实现转账成功和转账异常时事务回滚
 *
 * 注意考虑以下情况：
 *
 * 1）注解有无value属性值【@service（value=""） @Repository（value=""）】
 *
 * 2）service层是否实现接口的情况【jdk还是cglib】
 *
 * 二、作业资料说明：
 *
 * 1、提供资料：代码工程、验证及讲解视频、SQL脚本。
 *
 * 2、讲解内容包含：题目分析、实现思路、代码讲解。
 *
 * 3、效果视频验证
 *
 *  1）实现转账成功和转账异常时事务回滚。
 *
 *  2）展示和讲解自定义@Service、@Autowired、@Transactional 注解类。
 *
 * 实现思路
 *
 * 问题1：在哪里个模块下写作业
 *  -- 肯定不能是引入注解包的模块，那就只能在（lagou-transfer, lagou-transfer-iocxml）里选，发现lagou-transfer-iocxml已经引入了Ioc容器，如此在自定义@Service注解没有意义
 *
 * 问题2：实现思路，先实现@Service、@Autowired，再在代理对象里实现事务控制
 *  1.找到注解@Service标识的类，实例化，初始化（属性注入）
 *  2.属性注入，从bean容器里获取，获取不到返回1中实例化
 *  3.创建和初始化完成后，判断是否需要增强（事务等），需要则创建代理对象对其增强
 *
 *  ----------------------------------------------------------------------
 *  六小时后进度
 *  已完成：
 *      1.识别注解类，实例化后放入缓存
 *      2.初始化实例，为AutoWired注解的属性赋值
 *  问题：
 *      1.为AutoWired注解的属性赋值时，根据接口类型找不到子类类型（）
 *      2.缓存中已经有servlet，且属性已赋值。但servlet中Service的值未初始化，如何将servlet的实例指向缓存中的servlet？
 *          -- 无需将servlet放入ioc容器
 *      3.缓存的value如何实现成单例呢？
 *          -- 未实现
 *  作业仍存在问题：
 *
 *
 * */
//@WebServlet(loadOnStartup = 1) // loadOnStartup > 0时，越小越先实例化。为0则最后。为-1则请求到了才实例化，默认-1
public class AnnotationBeanFactory extends BeanFactory {

    /**
     * 静态代码块
     * 1.识别出被@MyService标注的类，实例化
     * 2.初始化识别出的类，为@Autowired注解的属性注入值，若其未被实例化则回到1中实例化
     *
     * */

    static {

        // 1.先把proxyFactory实例化+初始化
        ProxyFactory proxyFactory = (ProxyFactory) singletonMap.get("proxyFactory");
        if(proxyFactory == null){
            try {
                proxyFactory = (ProxyFactory) createBean(ProxyFactory.class.getName());
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }

        // 2.获取所有被 @MyService 注解的类，遍历然后实例化初始化并放入Ioc
        Reflections reflections = new Reflections("com.lagou.edu");
        Set<Class<?>> myServiceAnnoClass = reflections.getTypesAnnotatedWith(MyService.class);
        for(Class<?> clazz : myServiceAnnoClass){
            // 判断当前类是否已在缓存里
            if(singletonMap.containsKey(clazz.getSimpleName())){
                continue;
            }
            try {
                // 获取注解时的value值（bean的id），若为空则使用类名的首字母小写
                MyService annotation = clazz.getAnnotation(MyService.class);
                // 2.1 beanId获取
                String beanName = annotation.value();
                if(beanName.equals("")){
                    String simpleName;
                    Class<?>[] interfaces = clazz.getInterfaces();
                    if(interfaces == null || interfaces.length == 0){
                        simpleName = clazz.getSimpleName();
                    }else if(interfaces.length == 1){
                        simpleName = interfaces[0].getSimpleName();
                    }else{
                        throw new Exception("解析@MyService注解失败：当前类实现多个接口且未指定id");
                    }
                    beanName = AnnotationBeanFactory.getLower(simpleName);
                }

                // 2.2 实例化 newInstance是使用默认的构造器创建对象，需该类提供无参构造器
                Object target = clazz.newInstance();

                // 2.3 初始化
                target = setProperty(target, clazz);

                // 2.4 如有必要返回代理对象（根据方法是否需要被增强）
                target = AnnotationBeanFactory.createProxyIfNecessity(target, clazz);

                // 将成型的bean放入缓存里
                singletonMap.put(beanName, target);
            } catch (Exception e) {
                System.out.println(e.getMessage());
            }
        }
    }


    /**
     * 创建代理类，如果需要增强方法时
     * */
    private static Object createProxyIfNecessity(Object target, Class<?> clazz){
        ProxyFactory proxyFactory = (ProxyFactory) singletonMap.get("proxyFactory");
        // 2.判断是否需求增强，是则返回代理类
        Object proxy = target;
        Method[] methods = clazz.getMethods();
        for(Method method : methods){
            Transactional transactionalAnno = method.getAnnotation(Transactional.class);
            if(transactionalAnno != null){
                proxy = proxyFactory.getJdkProxy(target);
                target = proxy;
            }
        }
        return proxy;
    }

    /**
     * 为target中被@MyAutowired注解的属性注入值
     * */
    private static Object setProperty(Object target, Class<?> clazz) throws Exception {
        Field[] fields = clazz.getDeclaredFields();
        // 遍历所有字段，将@MyAutowired标记的字段注入属性
        for(Field field : fields){
            MyAutowired annotation = field.getAnnotation(MyAutowired.class);
            if(annotation != null){
                if(annotation.byType()){
                    Class<?> type = field.getType();
                    String fieldSimpleName = type.getSimpleName();
                    Object fieldValue = singletonMap.get(AnnotationBeanFactory.getLower(fieldSimpleName));
                    if(fieldValue == null){
                        fieldValue = createBean(type.getName());
                    }
                    field.setAccessible(true);
                    field.set(target, fieldValue);
                }
            }
        }
        return target;
    }

    /**
     * 创建bean
     * */
    private static Object createBean(String name) throws ClassNotFoundException {
        if(name == null || name == ""){
            return null;
        }
        Class<?> clazz = Class.forName(name);
        Object target = null;
        try {
            // newInstance是使用默认的构造器创建对象，需该类提供无参构造器
            if(clazz.isInterface()){
//                Reflections reflection = new Reflections(name);
//                Class<?> clazz1 = Class.forName(name);
//                Set<Class<?>> subTypesOf = reflection.getSubTypesOf(Class<clazz1>);
                name += "Impl";
                clazz = Class.forName(name);
            }else {
                target = clazz.newInstance();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        // 初始化
        Field[] fields = clazz.getDeclaredFields();
        // 遍历所有字段，将@MyAutowired标记的字段注入属性
        for(Field field : fields){
            MyAutowired annotation = field.getAnnotation(MyAutowired.class);
            if(annotation != null){
                if(annotation.byType()){
                    Class<?> type = field.getType();
                    String fieldSimpleName = type.getSimpleName();
                    Object fieldValue = singletonMap.get(AnnotationBeanFactory.getLower(fieldSimpleName));
                    if(fieldValue == null){
                        fieldValue = createBean(type.getName());
                    }
                    try {
                        field.setAccessible(true);
                        field.set(target, fieldValue);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        singletonMap.put(AnnotationBeanFactory.getLower(clazz.getSimpleName()), target);
        return target;
    }

    public static String getLower(String simpleName){
        return simpleName.substring(0,1).toLowerCase() + simpleName.substring(1);
    }

}

//                    String beanName = fieldSimpleName.substring(0,1).toLowerCase() + fieldSimpleName.substring(1);
//                    if(beanName.equals("jdbcAccountDaoImpl")){
//                        fieldValue = singletonMap.get("accountDao");
//                    }
